﻿// -----------------------------------------------------------------------
// <copyright file="Mapper.cs" company="">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------

namespace HelpersAndContracts.Helpers
{
    using System;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Security.Cryptography;
    using System.Text;

    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    public static class Mapper
    {
        public static T Clone<T>(this T t) where T : class, new()
        {
            return Clone(t, new T());
        }

        public static T Clone<T>(this T t, T destanation) where T : class, new()
        {
            return Map(t, destanation);
        }

        public static TResult Map<TResult>(this object t) where TResult : class, new()
        {
            return Map(t, new TResult());
        }

        public static TResult Map<TResult>(this object source, TResult destanation) where TResult : class, new()
        {
            var sourceProperties = source.GetType().GetProperties();
            var resultProperties =
                typeof(TResult).GetProperties().Where(
                    property => property.CanWrite && property.GetSetMethod(false) != default(MethodInfo)).ToArray();

            foreach (var property in sourceProperties)
            {
                if (
                    resultProperties.Any(
                        prop => prop.Name == property.Name))// && prop.PropertyType == property.PropertyType))
                {
                    var value = property.GetValue(source, null);

                    if (value!= null)
                    {
                        destanation.GetType().GetProperty(property.Name).SetValue(destanation, value.ToString(), null); 
                    }
                }
            }

            return destanation;
        }

        public static TResult MapAndEncode<TResult>(this object t, Aes aes, byte[] secreteKey) where TResult : class, new()
        {
            return MapAndEncode(t, new TResult(), aes, secreteKey);
        }

        public static TResult MapAndDecode<TResult>(this object t, byte[] secretKey, byte[] initializationVector) where TResult : class, new()
        {
            return MapAndDecode(t, new TResult(), secretKey, initializationVector);
        }

        public static TResult MapAndEncode<TResult>(this object source, TResult destanation, Aes aes, byte[] secreteKey) where TResult : class, new()
        {
            var sourceProperties = source.GetType().GetProperties();
            var resultProperties =
                typeof(TResult).GetProperties().Where(
                    property => property.CanWrite && property.GetSetMethod(false) != default(MethodInfo)).ToArray();

            foreach (var property in sourceProperties)
            {
                if (
                    resultProperties.Any(
                        prop => prop.Name == property.Name))
                {
                    var value = property.GetValue(source, null);

                    if (value != null)
                    {
                        destanation.GetType().GetProperty(property.Name).SetValue(destanation, EncodeMessage(value.ToString(), aes, secreteKey), null);
                    }
                }
            }

            return destanation;
        }

        public static TResult MapAndDecode<TResult>(this object source, TResult destanation, byte[] secretKey, byte[] initializationVector) where TResult : class, new()
        {
            var sourceProperties = source.GetType().GetProperties();
            var resultProperties =
                typeof(TResult).GetProperties().Where(
                    property => property.CanWrite && property.GetSetMethod(false) != default(MethodInfo)).ToArray();

            foreach (var property in sourceProperties)
            {
                if (
                    resultProperties.Any(
                        prop => prop.Name == property.Name))
                {
                    var value = property.GetValue(source, null) as byte[];

                    if (value != null)
                    {
                        destanation.GetType().GetProperty(property.Name).SetValue(destanation, DecodeMessage(value, secretKey, initializationVector), null);
                    }
                }
            }

            return destanation;
        }


        private static byte[] EncodeMessage(string secretMessage, Aes aes, byte[] secretKey)
        {
            aes.Key = secretKey;
            using (MemoryStream ciphertext = new MemoryStream())
            using (CryptoStream cs = new CryptoStream(ciphertext, aes.CreateEncryptor(), CryptoStreamMode.Write))
            {
                byte[] plaintextMessage = Encoding.UTF8.GetBytes(secretMessage);
                cs.Write(plaintextMessage, 0, plaintextMessage.Length);
                cs.Close();
                return ciphertext.ToArray();
            }
        }

        public static string DecodeMessage(byte[] encryptedMessage, byte[] secretKey, byte[] initializationVector)
        {

            using (Aes aes = new AesCryptoServiceProvider())
            {
                aes.Key = secretKey;
                aes.IV = initializationVector;
                // Decrypt the message 
                using (MemoryStream plaintext = new MemoryStream())
                {
                    using (CryptoStream cs = new CryptoStream(plaintext, aes.CreateDecryptor(), CryptoStreamMode.Write))
                    {
                        cs.Write(encryptedMessage, 0, encryptedMessage.Length);
                        cs.Close();
                        string message = Encoding.UTF8.GetString(plaintext.ToArray());
                        return message;
                    }
                }
            }
        }

        private static byte[] ObjectToByteArray(Object obj)
        {
            if (obj == null)
                return null;
            BinaryFormatter bf = new BinaryFormatter();
            MemoryStream ms = new MemoryStream();
            bf.Serialize(ms, obj);
            return ms.ToArray();
        }
    }

}
